/*
 * KeyOut.java
 *
 * Created on August 7, 2007, 2:11 AM
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package io;

import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;
import javax.swing.KeyStroke;
import tal.drivechain.chain.AbortChainException;
import tal.drivechain.parsing.ParsingAid;
import tal.drivechain.matchers.VarMatcher;
import tal.drivechain.plugin.Channel;
import tal.drivechain.plugin.Module;
import tal.drivechain.variable.SetProperty.Flag;
import tal.drivechain.variable.Variable;

/**
 * trigger=keystroke
 * @author Main
 */
public final class KeyOut extends Module {

    private List<Object[]> triggerStrokes = null;
    private Robot r = null;
    public static final int KEY_PRESS = 0, KEY_RELEASE = 1, KEY_CLICK = 2;

    @Override
    public void setupProperties() throws AbortChainException {
        setTriggerStrokes(props.getProperty("triggerStrokes"), null);
    }

    @Override
    public void start() {
        super.start();
        try {
            r = new Robot();
            r.setAutoWaitForIdle(true);
        } catch (AWTException ex) {
            throw new IllegalArgumentException("Error while initializing key robot: " + ex.getMessage());
        }
    }

    @Override
    public void stop() {
        r = null;
        super.stop();
    }

    public void setTriggerStrokes(Object value, Flag flag) {
        List<Object[]> newTrigStrk = (List<Object[]>)value;
        if (flag == Flag.ADD)
            triggerStrokes = newTrigStrk;
        else {
            if (triggerStrokes==null) triggerStrokes = new ArrayList<Object[]>();
            triggerStrokes.addAll(newTrigStrk);
        }
    } public Object getTriggerStrokes() { return triggerStrokes; }

    
    @Override
    protected void msgReceived(Variable in, long timestamp, Channel channel) {
        this.setInVars(in, timestamp, channel);
        for (Object[] item : triggerStrokes) {
            VarMatcher trigger = (VarMatcher)item[0];
            try {            
                if (trigger.matches(in, channel, this)) {
                    boolean release = false;
                    List<int[]> strokes = (List<int[]>)item[1];
                    for (int[] stroke : strokes) {
                        int type = stroke[0];
                        int key = stroke[1];
                        if (type==KEY_PRESS || type==KEY_CLICK) {
                            if (debugFlag) log("Sending key press event - " + KeyEvent.getKeyText(key) + " (" + key + ")", MSG.DEBUG);
                            r.keyPress(key);
                            if (type==KEY_CLICK) release = true;
                        } else if (type==KEY_RELEASE) {
                            if (debugFlag) log("Sending key release event - " + KeyEvent.getKeyText(key) + " (" + key + ")", MSG.DEBUG);
                            r.keyRelease(key);
                        }
                    }
                    if (release) {
                        for (int[] stroke : strokes) {
                            if (debugFlag) log("Sending key release event - " + KeyEvent.getKeyText(stroke[1]) + " (" + (stroke[1]) + ")", MSG.DEBUG);
                            r.keyRelease(stroke[1]);
                        }
                    }
                }
            } catch (IllegalArgumentException ie) {
                log(ie.getMessage(), MSG.WARNING);
            }

        }
    }

    public static Object parseStrokes(String prop, HashMap<String,String> attributes) {
       //event:name+name+name+...+name;event:name+name+name
        List<Object[]> ret = new ArrayList<Object[]>();
        StringTokenizer eventTokenizer = new StringTokenizer(prop.toLowerCase(), ";");
        while (eventTokenizer.hasMoreTokens()) {
            String stroke = eventTokenizer.nextToken().trim();
            int strokeType;

            int dotsIndex = stroke.indexOf(':');
            if (dotsIndex==-1) throw new IllegalArgumentException("Illegal key out format: " + stroke);
            if (stroke.startsWith("press:") || stroke.startsWith("prs:")) strokeType = KEY_PRESS;
            else if (stroke.startsWith("release:") || stroke.startsWith("rls:")) strokeType = KEY_RELEASE;
            else if (stroke.startsWith("click:") || stroke.startsWith("clk:")) strokeType = KEY_CLICK;
            else throw new IllegalArgumentException("Unknown key event: " + stroke.substring(0, dotsIndex));
            stroke = stroke.substring(dotsIndex+1);
            KeyStroke s = ParsingAid.parseKeyStroke(stroke);
            ret.add(new Object[] {strokeType, s});
        }
        return ret;
    }

    public static String strokesToString(Object prop, HashMap attributes) {
        List<Object[]> strokes = (List<Object[]>)prop;
        String ret = "";
        int lastType = -1;
        for (Object[] s : strokes) {
            int type = (Integer)s[0];
            KeyStroke keys = (KeyStroke)s[1];
            String sCode = ParsingAid.keyStrokeToName(keys).trim();
            if (type==lastType)
                ret += "+" + sCode;
            else ret += "; " + strokeTypeToString(type) + ": " + sCode;
            lastType = type;
        }
        return ret.substring(2);
    }

    private static String strokeTypeToString(int type) {
        if (type==KEY_PRESS)
            return "press";
        else if (type==KEY_RELEASE)
            return "release";
        else if (type==KEY_CLICK)
            return "click";
        else throw new IllegalArgumentException("Unknown stroke type code: " + type);
    }

}
